/*
 * EncryptUtil.java
 * Copyright (c) 2014, 2019, Kiri. 
 * All rights reserved.
 */
package com.onesalt.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import org.springframework.security.crypto.codec.Base64;

import com.onesalt.common.BaseException;

/**
 * md5
 *
 * @author kiri
 * @date 2015年4月19日
 * @version 1.0
 */
public class EncryptUtil {

//	private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);

	private static final String ALGORITHM_MD5 = "MD5";

	private static final String ALGORITHM_DES = "DES";

	private static final String CHARSET_UTF8 = "UTF-8";

	private static final String PADDING_CHAR = "0";

	private static boolean isNull(String s) {
		return s == null || s.trim().length() == 0;
	}

	public static String encodeUserId(long userId) {
		return encodeUserId(userId, 100 * 365 * 24 * 3600L); // WON'T EXPIRE
															// WITHIN 100
															// YEARS!
	}

	public static String encodeUserId(long userId, long expire) {
		String plainText = userId + Constant.TOKEN_SPERATOR + System.currentTimeMillis();
		return encodeToken(plainText, expire); // WON'T EXPIRE WITHIN 100 YEARS!
	}

	/**
	 * 
	 * @param plainText
	 * @param expire the unit of expire is seconds
	 * @return
	 */
	public static String encodeToken(String plainText, long expire) {
		String plainTextStr = plainText + Constant.TOKEN_SPERATOR + expire;
		return EncryptUtil.encryptByDes(plainTextStr, Constant.TOKEN_DES_KEY);
	}

	/**
	 * 
	 * @param tokenStr
	 * @return token array, include expire date in seconds
	 */
	public static String decodeToken(String tokenStr) {
		if (isNull(tokenStr)) {
			return null;
		}
		String plainText = null;
		try {
			plainText = EncryptUtil.decryptByDes(tokenStr, Constant.TOKEN_DES_KEY);
		} catch (Exception e) {
			return null;
		}
		return plainText;
	}

	public static long getUserId(String token) {
		long userId = 0;
		if (!isNull(token)) {
			String tokenPlainText = EncryptUtil.decodeToken(token);
			if (null == tokenPlainText) {
				return 0L;
			}
			String[] tokenArray = tokenPlainText.split(Constant.TOKEN_SPERATOR);
			if (tokenArray != null && tokenArray.length == 3) {
				userId = Long.valueOf(tokenArray[0]);
				return userId;
			}
		}
		return 0L;
	}

	/**
	 * Encrypt by MD5
	 * 
	 * @param plainText
	 * @return 16 chars which are in the middle of the 32 cipher text.
	 */
	public static String md5(String plainText) {
		int intDigest = 0;
		MessageDigest md5 = null;
		byte[] digest = null;
		StringBuffer cipherText = new StringBuffer();
		try {
			md5 = MessageDigest.getInstance(ALGORITHM_MD5);
		} catch (NoSuchAlgorithmException e) {
			throw new BaseException("加密失败", e);
		}
		digest = md5.digest(plainText.getBytes());

		for (int i = 0; i < digest.length; i++) {
			intDigest = digest[i] & 0xFF;
			if (intDigest < 16) {
				cipherText.append(PADDING_CHAR);
			}
			cipherText.append(Integer.toHexString(intDigest));
		}
		return cipherText.toString();
	}

	public static byte[] encryptByteByMd5(String plainText) {
		MessageDigest md5 = null;
		byte[] digest = null;
		try {
			md5 = MessageDigest.getInstance(ALGORITHM_MD5);
		} catch (NoSuchAlgorithmException e) {
			throw new BaseException("加密失败", e);
		}
		digest = md5.digest(plainText.getBytes());

		return digest;
	}

	/**
	 * Encrypt by DES
	 * 
	 * @param plainText
	 * @param key desKey, its length should be more that 8.
	 * @return cipher text
	 */
	public static String encryptByDes(String plainText, String key) {
		ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
		byte[] bytePlainText = plainText.getBytes();

		while (key.length() < 8) {
			key += PADDING_CHAR;
		}

		try {
			DESKeySpec desKey = new DESKeySpec(key.getBytes());
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM_DES);
			Key secretKey = keyFactory.generateSecret(desKey);

			Cipher cipher = Cipher.getInstance(ALGORITHM_DES);
			cipher.init(Cipher.ENCRYPT_MODE, secretKey);

			int position = 0;
			int plainTextLength = bytePlainText.length;
			int blockSize = cipher.getBlockSize();

			for (int i = 0; i <= plainTextLength - blockSize; i = position) {
				cipherText.write(cipher.update(bytePlainText, position, blockSize));
				position += blockSize;
			}

			if (position < plainTextLength) {
				cipherText.write(cipher.doFinal(bytePlainText, position, plainTextLength - position));
			} else {
				cipherText.write(cipher.doFinal());
			}

		} catch (Exception e) {
			throw new BaseException("加密失败", e);
		} finally {
			try {
				cipherText.close();
			} catch (IOException e) {
				throw new BaseException("加密失败", e);
			}
		}

		byte[] encryptedData = cipherText.toByteArray();
		try {
			return new String(Base64.encode(encryptedData), CHARSET_UTF8);
		} catch (UnsupportedEncodingException e) {
			throw new BaseException("加密失败", e);
		}
	}

	/**
	 * Decrypt by DES
	 * 
	 * @param cipherText
	 * @param key key desKey, its length should be more that 8.
	 * @return
	 */
	public static String decryptByDes(String cipherText, String key) {
		ByteArrayOutputStream plainText = new ByteArrayOutputStream();
		byte[] byteCipherText = null;
		try {
			byteCipherText = Base64.decode(cipherText.getBytes(CHARSET_UTF8));
		} catch (UnsupportedEncodingException e) {
			throw new BaseException("解密失败", e);
		}

		if (byteCipherText == null) {
			throw new BaseException("解密失败");
		}

		while (key.length() < 8) {
			key += PADDING_CHAR;
		}

		try {
			DESKeySpec desKey = new DESKeySpec(key.getBytes(CHARSET_UTF8));
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM_DES);
			Key secretKey = keyFactory.generateSecret(desKey);

			Cipher cipher = Cipher.getInstance(ALGORITHM_DES);
			cipher.init(Cipher.DECRYPT_MODE, secretKey);

			int position = 0;
			int cipherTextLength = byteCipherText.length;
			int blockSize = cipher.getBlockSize();

			for (int i = 0; i <= cipherTextLength - blockSize; i = position) {
				plainText.write(cipher.update(byteCipherText, position, blockSize));
				position += blockSize;
			}

			if (position < cipherTextLength) {
				plainText.write(cipher.doFinal(byteCipherText, position, cipherTextLength - position));
			} else {
				plainText.write(cipher.doFinal());
			}

		} catch (Exception e) {
			throw new BaseException("解密失败", e);
		} finally {
			try {
				plainText.close();
			} catch (IOException e) {
				throw new BaseException("解密失败", e);
			}
		}
		String plainTextStr = "";
		try {
			plainTextStr = new String(plainText.toByteArray(), CHARSET_UTF8);
		} catch (UnsupportedEncodingException e) {
			throw new BaseException("解密失败", e);
		}
		return plainTextStr;
	}
}
